package br.org.bertol.mestrado.engine.optimisation.pso;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.ListIterator;

import org.apache.log4j.Logger;

import br.org.bertol.mestrado.engine.Objective;
import br.org.bertol.mestrado.engine.exception.HeuristicsException;
import br.org.bertol.mestrado.engine.optimisation.AbstractMetaHeuristica;
import br.org.bertol.mestrado.engine.optimisation.entity.Particle;
import br.org.bertol.mestrado.engine.optimisation.moo.ParetoDominace;
import br.org.bertol.mestrado.engine.optimisation.sls.IteratedLocalSearch;

/**
 * @author contaqualquer
 */
public class ParticleSwarmOptimization extends
        AbstractMetaHeuristica<Integer, Particle> {

    /***/
    private final transient TypeMovement  tipoMovimento;

    /***/
    private final transient PsoRepository psoRepository;

    /**
     * @param verify
     *            Objeto
     * @param swarm
     *            Objeto
     * @param typeMovimento
     *            Objeto
     * @param numIteracoes
     *            Objeto
     * @param depth
     *            Objeto
     * @param repositorySize
     *            Objeto
     */
    public ParticleSwarmOptimization(final List<Particle> swarm,
        final TypeMovement typeMovimento,
        final long numIteracoes, final int depth, final int repositorySize,
        final ArrayList classes, final Objective[] objectives) {

        super(numIteracoes, classes, objectives);

        this.listaPareto.addAll(swarm);

        this.tipoMovimento = typeMovimento;

        psoRepository = new PsoRepository(depth,
                repositorySize, objectives);

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void evaluate() throws HeuristicsException {

        try {
            initSwarm();

            for (int kIteration = 0; kIteration < numIteracoes; kIteration++) {
                evaluateSwarmParticle(kIteration);
            }
        } catch (Exception e) {
            throw new HeuristicsException(e);
        }
    }

    /**
     * Rezalia uma iteração do enxame.
     * @param kIteration
     *            iteração atual
     * @throws Exception
     *             Erro
     */
    private void evaluateSwarmParticle(final int kIteration) throws Exception {

        Particle gBest = null;

        // recupera melhor global
        if (objectives.length == 1) {
            gBest = psoRepository.getGbest();
        }

        for (final ListIterator<Particle> iterator = listaPareto.listIterator(); iterator
                .hasNext();) {

            final Particle particle = iterator.next();

            if (objectives.length > 1) {
                gBest = psoRepository.getGbest();
            }
            // atualiza gbest
            particle.updateGbest(gBest);

            // atualiza velocidade
            particle.updateVelocity(kIteration);

            // virificação in�cio: clona
            final Particle dummy = (Particle) particle.clone();

            // atualiza posi��o do clone
            tipoMovimento.execute(dummy);

            // verifica se � v�lido movimento
            // verify.verifyStrongConstraint(dummy, true);

            // caso movimenot v�lido
            // executa
            if (dummy.calcFitness()) {

                particle.calcFitness();

                final Particle newParticle = applyILS(particle);
                /*
                 * // atualiza posição tipoMovimento.execute(newParticle); //
                 * calcula novo peso da solu��o
                 * newParticle.evaluateConstraint(verify);
                 */
                if (paretoMinMinDominace.compare(newParticle.getHeight(),
                                                 newParticle
                        .getHpbest()) != ParetoDominace.RIGHT_DOMINE) {
                    newParticle.updatePBest();
                }

                iterator.set(newParticle);

            }
        }

        // adiciona a part�cula no reposit�rio
        psoRepository.addInto(listaPareto);
    }

    /**
     * Inicia o Swarm.
     * @throws Exception
     *             Erro
     */
    private void initSwarm() throws Exception {

        for (final ListIterator<Particle> iterator = listaPareto.listIterator(); iterator
                .hasNext();) {

            final Particle particle = iterator.next();

            final Particle newParticle = applyILS(particle);

            // atualiza posi��o
            tipoMovimento.execute(newParticle);

            // calcula novo peso da solu��o
            // newParticle.evaluateConstraint(verify);

            newParticle.calcFitness();

            newParticle.updatePBest();

            iterator.set(newParticle);
        }

        psoRepository.startParetoBorder(listaPareto);

    }

    /**
     * Aplica busca ILS.
     * @param particle
     *            Partícula a ser melhorada
     * @return Partícula melhorada
     * @throws Exception
     *             Erro
     */
    private Particle applyILS(final Particle particle) throws Exception {

        Logger.getLogger(getClass())
                .debug(
                       "Unidade pr� ILS ---------------------------- : "
                + particle);

        final IteratedLocalSearch<Integer, Particle> ils =
                new IteratedLocalSearch<Integer, Particle>(
                particle, 50, classes, objectives);

        ils.evaluate();

        final Particle newParticle = ils.getBetterUnit();

        Logger.getLogger(getClass())
                .debug(
                       "Unidade pós ILS ---------------------------- : "
                + newParticle);

        if (paretoMinMinDominace.compare(newParticle.getHeight(), particle
                .getHeight()) == ParetoDominace.LEFT_DOMINE) {
            newParticle.update();
        }

        return newParticle;
    }

    /**
     * Retorna todo o bando.
     * @return {@link List}
     */
    public final List<Particle> getSwarm() {
        return listaPareto;
    }

    /**
     * @see PsoRepository#getParetoBorder()
     * @return Lista de soluções não dominadas
     */
    public final List<Particle> getParetoBorder() {
        return psoRepository.getParetoBorder();
    }
}